因为正则表达式的在文本处理,字符串匹配中有着重要应用,因此本文对其基本语法规则及在Python中的应用进行了简要介绍。

注:本文大部分是对博文正则表达式30分钟入门教程LiZeC的正则表达式笔记以及python re库官方文档的归纳和整理,更详细内容可查阅原文。

基本语法

  • 基本概念: 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来匹配、检测、替换特定的字符串等。

字符

  • 普通字符:通常正则表达式中出现的任意一个字符代表匹配和他们一样的字符。
  • 特殊字符:特殊字符并不匹配他们本身,而是有特殊含义的,具体有:. ^ $ * + ? { } [ ] \ | ( )
    若要匹配这些字符本身,则需要加上\进行转义(escape),如\*表示匹配*本身。

特殊字符含义

字符 含义
. 匹配任意单个字符(除换行符“\n”外)
\w 匹配任意字母、数字、下划线、汉字(即匹配普通字符)
\s 匹配任意的空白符(空格、制表符(Tab)、换行符、中文全角空格等)
\d 匹配数字(0-9)
\b 匹配单词的开始或结束(单词边界),即非字母数字的任意其他字符
^ 匹配字符串的开始
$ 匹配字符串的结束
  • 对单词边界\b:如正则表达式\blove\b,就会匹配句子”I love you”中的love, 而不会匹配”I aloveb you”中的love。

  • 一些字符的反义表示
    \w\b这些特殊字符的大写表示相反的含义,具体如下:

字符 含义
\W 匹配不是字母、数字、下划线、汉字的字符(多用来匹配特殊字符)
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置

重复匹配限定符

以下字符称为“限定符”(限制次数的符号),跟在特殊字符的后面,表示重复该字符特定次数。
\d+匹配1个或更多个的数字。

限定符 含义
* 重复\(\geq\)0次
+ 重复\(\geq\)1次
? 重复0次或1次
{n} 重复n次
{n,} 重复\(\geq\)n次
{n,m} 重复n到m次

贪婪、懒惰匹配原则

  • 正则表达式的匹配原则为贪婪匹配:即在满足条件的情况下,匹配尽可能多的字符。
    eg. a.*b会匹配以a开始,以b结束的最长的字符串。如果用来搜索aabab,则会匹配整个字符串aabab而非aab
  • 若需要懒惰匹配:即匹配尽可能少的字符,就在相应的重复字符?即可。
    eg. a.*?b匹配以a开始,以b结束的最短的字符串,搜索aabab会匹配到aabab两个子串(之所以没有仅仅匹配到ab子串,是因为正则表达式的匹配还会考虑开始的先后顺序,最开始匹配的优先级最高)。
  • 懒惰匹配限定符如下:
限定符 含义
*? 重复\(\geq\)0次,但尽可能少重复
+? 重复\(\geq\)1次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,}? 重复\(\geq\)n次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复

字符类

  • [] 中的若干字符构成一个字符类(character class)。
  • 字符类是为了匹配某种字符集合,表示此位置可以匹配这个类中的任意一个字符。
  • 整个字符类所起到的作用和普通字符相同,都是只匹配单个字符,因此字符类可作为整体再接受其他限定,如? + {n,m}等等。
  • 可以使用-来表示一个范围,例如[a-c]表示[abc]
  • 在字符类中的特殊符号不被转义
  • 反向匹配:在字符类中,如果以^开头,则表示匹配除此字符类中提及的任何其他字符。如[^5]匹配任何不是5的字符。

分枝条件

  • 在正则表达式表示“或”的逻辑,两个条件用|连接即可。
    eg. \d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。
  • 在使用分枝条件时要注意各个条件的顺序,如果上文改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。因为一旦前面的分枝满足的话就不会再管其他条件了。

注释

不推荐在正则表达式内部写注视,注释推荐写在正则表达式的外部语言中。

正则表达式高级特性

分组

  • 上文提到了如何重复单个字符(直接在字符后面加表示重复的限定符即可),但若想重复多个字符,就需要用到分组的概念。
  • 用小括号()来指定分组(也叫子表达式),之后就可以对这个分组的整体进行重复或其他操作。
    eg. (\d{1,3}\.){3}\d{1,3},就是一个比较粗糙的IP地址匹配式。

后向引用

  • 使用小括号指定一个分组后,这个分组可以作为一个整体在后文中作进一步处理。
  • 默认情况下,每个组会有一个组号,从左到右,第一个出现的分组组号为1(注意不是0),第二个为2,以此类推。
  • 如何引用:在后问中使用\+组号的形式来引用,如\1表示引用1号分组的。
    eg. \b(\w+)\b\s+\1\b可以来匹配如go go 或kitty kitty 这种连着两个重复单词。

零宽断言

  • 用于查找在某些内容前面或后面的东西(但不包括这些内容),类似于^ $ \b这种占位符,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此被称为零宽断言。
  • (?=exp):用于匹配exp前面出现的表达式。
    e.g \b\w+(?=ing\b)用于匹配以ing为结尾的单词的前面部分(不包括ing)
  • (?<=exp):用于匹配exp后面出现的表达式。
    e.g (?<=\bre)\w+\b用于匹配以re开头的单词的后面部分(不包括re)
  • 更多示例:
    (?<=\s)\d+(?=\s):匹配以空白符间隔的数字(不包括这些空白符)
    ((?<=\d)\d{3})+\b:要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分(用它对1234567890进行查找时结果是234567890

负向零宽断言

  • 用于确保某个模式不会出现。与字符类[^exp]的区别:虽然不匹配这个字符,但字符类总是会匹配某个字符的,这会限制字符类的应用场景;而负向零宽断言不匹配字符,其只指代一个位置。
    eg. 如果用\b\w*q[^u]\w*\b来匹配“出现了字母q,但是q后面跟的不是字母u”的单词,则像“Iraq, Benq”这种q直接作为最后一个字符的情况就会出错(字符类总要匹配一个字符),因此就需要负向零宽断言。
  • (?!exp):断言此位置的后面不能匹配表达式exp。
    eg.\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词
  • (?<!exp):断言此位置的前面不能匹配表达式exp。
    eg. (?<![a-z])\d{7}匹配前面不是小写字母的七位数字。

递归匹配

  • 用于匹配一些嵌套的层次结构,如(100*(50+15)),如果只是简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容。假如原来的字符串里的左括号和右括号出现的次数不相等,比如(5/(3+2))),那我们的匹配结果里两者的个数也不会相等。如果想要想匹配到最长的,而且配对正确的字符串,就需要用到递归匹配。
  • 具体内容参见博文正则表达式30分钟入门教程

经典正则表达式实例

正则表达式 解释
^[A-Za-z]+$ 由26个字母组成的字符串
^[A-Za-z0-9]+$ 由26个字母和数字组成的字符串
^-?\d+$ 整数形式的字符串
^[0-9]*[1-9][0-9]*$ 正整数形式的字符串
[\u4e00-\u9fa5] 判断是不是中文字符

在Python中使用正则表达式

使用原生字符串

  • 正则表达式中使用\n表示转义,而python中恰好也使用\n表示转义,因此在python中使用正则表达式则需要\\来表示反斜杠。
  • 为了节省过多的反斜杠,可以使用Python原生字符串特性,即在字符串开头加上r,如r\d,在这个字符串中每个字符表示其本身,Python不进行转义。

re库

在Python中使用正则表达式直接导入re库即可 import re

常用函数介绍

函数 功能
re.compile() 编译正则表达式,返回Regular Expression Objects对象
re.search() 在字符串中查找匹配的子串第一次出现的位置,匹配成功返回match对象否则返回None
re.findall() 在字符串中查找所有满足条件的子串,返回string list
re.match() 强制从起始位置开始匹配,匹配成功返回match对象否则返回 None
re.split() 将正则表达式作为separator来分割字符串,返回string list
re.sub() 在字符串中替换所有匹配正则表达式的子串,返回替换后的字符串
re.escape() 自动在字符串中添加 “\” 转义符(除去字母数字下划线),返回修改后的字符串
可用来自动生成包含特殊字符的正则表达式

参数说明:

  • re.compile(pattern, flags=0)
    pattern: 表示正则表达式的字符串
    flags: 正则表达式的控制标记
  • re.search(pattern, string, flags=0)
    pattern:同上
    string:待查找的字符串
    flags: 同上
  • re.findall(pattern, string, flags=0)
    参数同上
  • re.match(pattern, string, flags=0)
    参数同上
    re.fullmatch(pattern, string, flags=0)
    参数同上,只不过是查找整个字符串
  • re.sub(pattern, repl, string, count=0, flags=0)
    repl: 用来替代匹配到的子串的字符串
  • re.escape(pattern)
    eg. pattern1 = re.escape('python.exe')),pattern1就为”python\.exe”,可直接作为pattern参数传入其他函数中,用来匹配“python.exe”。当pattern含有大量特殊字符时使用这个函数就很方便。
str.replace()和re.sub的比较
  • str.replace(old, new[, count])是字符串的替代函数,其中new为替换的字符串,old为待替换的字符串,old只能为substring而不能为字符串,因此替换功能较为简单。
  • re.sub(pattern, repl, string, count=0, flags=0)则使用了正则表达式,可进行更复杂的替换操作,但同时开销也更大。
  • 因此能用replace()尽量用,复杂的替换操作再使用正则表达式。

控制标记flag介绍

标记名(简写/全称) 含义
re.A / re.ASCII 使\w,\b,\s和\d只匹配ASCII字符
eg. 不会匹配汉字和其他Unicode字符
re.I / re.IGNORECASE 忽略正则表达式大小写
re.M / re.MULTILINE 使得^表示每一行的开始,$表示每一行的结束
原义仅表示一个单词的开始和结束
re.S / re.DOTALL 使得.可以匹配\n字符
re.X / re.VERBOSE 忽略正则表达式内部的空白符

注:这些变量在VSCode的python下没有提示,但可以运行。

正则表达式的两种使用方法

  • 直接函数调用:result = re.search(pattern, string)

  • 先编译后使用:
    prog = re.compile(pattern)
    result = prog.search(string)

  • 两种方式效果相同,且因为缓存机制,在正则表达式数量较少的的情况下,两者效率也相近;但如果重复地调用很多正则表达式,先编译好的效率会更高。

Match类

re.search()和re.match()两个函数的返回对象,包含了匹配的相关信息,常用函数和属性如下:

名称 说明
match.group() 返回特定的分组,string或tuple的形式
match.groups() 以tuple的形式返回所有分组
match.start() 返回特定分组的起始地址
match.end() 返回特定分组的结束地址
(会比实际大1位,因为python字符串截取前闭后开的特性)
match.re 为编译好的regular expression object对象
match.string 为传入到re.search()或re.match()的待匹配字符串

参数说明:

  • match.group([group1, …])
    group1为组号,默认为0(这时返回the whole match);当没有参数或只有一个参数时返回string,当有2个或以上参数时返回tuple。
  • match.groups(default=None)
    default为没有匹配到的分组指定所显示的名字,通常保持默认即可。
  • match.start([group])、match.end([group])
    两个函数中group都是指组号,返回特定组号的起始、终止地址;
    因此相应分组的子串可通过m.string[m.start(g):m.end(g)]来获取。

Post Date: 2019-02-07

版权声明: 本文为原创文章,转载请注明出处